/*
 * Copyright (C) 2008 Nir Tzachar <nir.tzachar@gmail.com?
 * Released under the terms of the GNU GPL v2.0.
 *
 * Derived from menuconfig.
 *
 */
#include "nconf.h"

/* a list of all the different widgets we use */
attributes_t attributes[ATTR_MAX + 1] = {0};

/* available colors:
   COLOR_BLACK   0
   COLOR_RED     1
   COLOR_GREEN   2
   COLOR_YELLOW  3
   COLOR_BLUE    4
   COLOR_MAGENTA 5
   COLOR_CYAN    6
   COLOR_WHITE   7
   */
static void set_normal_colors(void)
{
	init_pair(NORMAL, -1, -1);
	init_pair(MAIN_HEADING, COLOR_MAGENTA, -1);

	/* FORE is for the selected item */
	init_pair(MAIN_MENU_FORE, -1, -1);
	/* BACK for all the rest */
	init_pair(MAIN_MENU_BACK, -1, -1);
	init_pair(MAIN_MENU_GREY, -1, -1);
	init_pair(MAIN_MENU_HEADING, COLOR_GREEN, -1);
	init_pair(MAIN_MENU_BOX, COLOR_YELLOW, -1);

	init_pair(SCROLLWIN_TEXT, -1, -1);
	init_pair(SCROLLWIN_HEADING, COLOR_GREEN, -1);
	init_pair(SCROLLWIN_BOX, COLOR_YELLOW, -1);

	init_pair(DIALOG_TEXT, -1, -1);
	init_pair(DIALOG_BOX, COLOR_YELLOW, -1);
	init_pair(DIALOG_MENU_BACK, COLOR_YELLOW, -1);
	init_pair(DIALOG_MENU_FORE, COLOR_RED, -1);

	init_pair(INPUT_BOX, COLOR_YELLOW, -1);
	init_pair(INPUT_HEADING, COLOR_GREEN, -1);
	init_pair(INPUT_TEXT, -1, -1);
	init_pair(INPUT_FIELD, -1, -1);

	init_pair(FUNCTION_HIGHLIGHT, -1, -1);
	init_pair(FUNCTION_TEXT, COLOR_YELLOW, -1);
}

/* available attributes:
   A_NORMAL        Normal display (no highlight)
   A_STANDOUT      Best highlighting mode of the terminal.
   A_UNDERLINE     Underlining
   A_REVERSE       Reverse video
   A_BLINK         Blinking
   A_DIM           Half bright
   A_BOLD          Extra bright or bold
   A_PROTECT       Protected mode
   A_INVIS         Invisible or blank mode
   A_ALTCHARSET    Alternate character set
   A_CHARTEXT      Bit-mask to extract a character
   COLOR_PAIR(n)   Color-pair number n
   */
static void normal_color_theme(void)
{
	/* automatically add color... */
#define mkattr(name, attr) do { \
attributes[name] = attr | COLOR_PAIR(name); } while (0)
	mkattr(NORMAL, NORMAL);
	mkattr(MAIN_HEADING, A_BOLD | A_UNDERLINE);

	mkattr(MAIN_MENU_FORE, A_REVERSE);
	mkattr(MAIN_MENU_BACK, A_NORMAL);
	mkattr(MAIN_MENU_GREY, A_NORMAL);
	mkattr(MAIN_MENU_HEADING, A_BOLD);
	mkattr(MAIN_MENU_BOX, A_NORMAL);

	mkattr(SCROLLWIN_TEXT, A_NORMAL);
	mkattr(SCROLLWIN_HEADING, A_BOLD);
	mkattr(SCROLLWIN_BOX, A_BOLD);

	mkattr(DIALOG_TEXT, A_BOLD);
	mkattr(DIALOG_BOX, A_BOLD);
	mkattr(DIALOG_MENU_FORE, A_STANDOUT);
	mkattr(DIALOG_MENU_BACK, A_NORMAL);

	mkattr(INPUT_BOX, A_NORMAL);
	mkattr(INPUT_HEADING, A_BOLD);
	mkattr(INPUT_TEXT, A_NORMAL);
	mkattr(INPUT_FIELD, A_UNDERLINE);

	mkattr(FUNCTION_HIGHLIGHT, A_BOLD);
	mkattr(FUNCTION_TEXT, A_REVERSE);
}

static void no_colors_theme(void)
{
	/* automatically add highlight, no color */
#define mkattrn(name, attr) { attributes[name] = attr; }

	mkattrn(NORMAL, NORMAL);
	mkattrn(MAIN_HEADING, A_BOLD | A_UNDERLINE);

	mkattrn(MAIN_MENU_FORE, A_STANDOUT);
	mkattrn(MAIN_MENU_BACK, A_NORMAL);
	mkattrn(MAIN_MENU_GREY, A_NORMAL);
	mkattrn(MAIN_MENU_HEADING, A_BOLD);
	mkattrn(MAIN_MENU_BOX, A_NORMAL);

	mkattrn(SCROLLWIN_TEXT, A_NORMAL);
	mkattrn(SCROLLWIN_HEADING, A_BOLD);
	mkattrn(SCROLLWIN_BOX, A_BOLD);

	mkattrn(DIALOG_TEXT, A_NORMAL);
	mkattrn(DIALOG_BOX, A_BOLD);
	mkattrn(DIALOG_MENU_FORE, A_STANDOUT);
	mkattrn(DIALOG_MENU_BACK, A_NORMAL);

	mkattrn(INPUT_BOX, A_BOLD);
	mkattrn(INPUT_HEADING, A_BOLD);
	mkattrn(INPUT_TEXT, A_NORMAL);
	mkattrn(INPUT_FIELD, A_UNDERLINE);

	mkattrn(FUNCTION_HIGHLIGHT, A_BOLD);
	mkattrn(FUNCTION_TEXT, A_REVERSE);
}

void set_colors()
{
	start_color();
	use_default_colors();
	set_normal_colors();

	if(has_colors()) {
		normal_color_theme();
	} else {
		/* give defaults */
		no_colors_theme();
	}
}


/* this changes the windows attributes !!! */
void print_in_middle(WINDOW* win,
                     int starty,
                     int startx,
                     int width,
                     const char* string,
                     chtype color)
{
	int length, x, y;
	float temp;


	if(win == NULL)
		win = stdscr;

	getyx(win, y, x);

	if(startx != 0)
		x = startx;

	if(starty != 0)
		y = starty;

	if(width == 0)
		width = 80;

	length = strlen(string);
	temp = (width - length) / 2;
	x = startx + (int)temp;
	(void) wattrset(win, color);
	mvwprintw(win, y, x, "%s", string);
	refresh();
}

int get_line_no(const char* text)
{
	int i;
	int total = 1;

	if(!text)
		return 0;

	for(i = 0; text[i] != '\0'; i++)
		if(text[i] == '\n')
			total++;

	return total;
}

const char* get_line(const char* text, int line_no)
{
	int i;
	int lines = 0;

	if(!text)
		return 0;

	for(i = 0; text[i] != '\0' && lines < line_no; i++)
		if(text[i] == '\n')
			lines++;

	return text + i;
}

int get_line_length(const char* line)
{
	int res = 0;

	while(*line != '\0' && *line != '\n') {
		line++;
		res++;
	}

	return res;
}

/* print all lines to the window. */
void fill_window(WINDOW* win, const char* text)
{
	int x, y;
	int total_lines = get_line_no(text);
	int i;

	getmaxyx(win, y, x);
	/* do not go over end of line */
	total_lines = min(total_lines, y);

	for(i = 0; i < total_lines; i++) {
		char tmp[x + 10];
		const char* line = get_line(text, i);
		int len = get_line_length(line);
		strncpy(tmp, line, min(len, x));
		tmp[len] = '\0';
		mvwprintw(win, i, 0, "%s", tmp);
	}
}

/* get the message, and buttons.
 * each button must be a char*
 * return the selected button
 *
 * this dialog is used for 2 different things:
 * 1) show a text box, no buttons.
 * 2) show a dialog, with horizontal buttons
 */
int btn_dialog(WINDOW* main_window, const char* msg, int btn_num, ...)
{
	va_list ap;
	char* btn;
	int btns_width = 0;
	int msg_lines = 0;
	int msg_width = 0;
	int total_width;
	int win_rows = 0;
	WINDOW* win;
	WINDOW* msg_win;
	WINDOW* menu_win;
	MENU* menu;
	ITEM* btns[btn_num + 1];
	int i, x, y;
	int res = -1;


	va_start(ap, btn_num);

	for(i = 0; i < btn_num; i++) {
		btn = va_arg(ap, char*);
		btns[i] = new_item(btn, "");
		btns_width += strlen(btn) + 1;
	}

	va_end(ap);
	btns[btn_num] = NULL;

	/* find the widest line of msg: */
	msg_lines = get_line_no(msg);

	for(i = 0; i < msg_lines; i++) {
		const char* line = get_line(msg, i);
		int len = get_line_length(line);

		if(msg_width < len)
			msg_width = len;
	}

	total_width = max(msg_width, btns_width);
	/* place dialog in middle of screen */
	y = (getmaxy(stdscr) - (msg_lines + 4)) / 2;
	x = (getmaxx(stdscr) - (total_width + 4)) / 2;


	/* create the windows */
	if(btn_num > 0)
		win_rows = msg_lines + 4;
	else
		win_rows = msg_lines + 2;

	win = newwin(win_rows, total_width + 4, y, x);
	keypad(win, TRUE);
	menu_win = derwin(win, 1, btns_width, win_rows - 2,
	                  1 + (total_width + 2 - btns_width) / 2);
	menu = new_menu(btns);
	msg_win = derwin(win, win_rows - 2, msg_width, 1,
	                 1 + (total_width + 2 - msg_width) / 2);

	set_menu_fore(menu, attributes[DIALOG_MENU_FORE]);
	set_menu_back(menu, attributes[DIALOG_MENU_BACK]);

	(void) wattrset(win, attributes[DIALOG_BOX]);
	box(win, 0, 0);

	/* print message */
	(void) wattrset(msg_win, attributes[DIALOG_TEXT]);
	fill_window(msg_win, msg);

	set_menu_win(menu, win);
	set_menu_sub(menu, menu_win);
	set_menu_format(menu, 1, btn_num);
	menu_opts_off(menu, O_SHOWDESC);
	menu_opts_off(menu, O_SHOWMATCH);
	menu_opts_on(menu, O_ONEVALUE);
	menu_opts_on(menu, O_NONCYCLIC);
	set_menu_mark(menu, "");
	post_menu(menu);


	touchwin(win);
	refresh_all_windows(main_window);

	while((res = wgetch(win))) {
		switch(res) {
			case KEY_LEFT:
				menu_driver(menu, REQ_LEFT_ITEM);
				break;

			case KEY_RIGHT:
				menu_driver(menu, REQ_RIGHT_ITEM);
				break;

			case 10: /* ENTER */
			case 27: /* ESCAPE */
			case ' ':
			case KEY_F(F_BACK):
			case KEY_F(F_EXIT):
				break;
		}

		touchwin(win);
		refresh_all_windows(main_window);

		if(res == 10 || res == ' ') {
			res = item_index(current_item(menu));
			break;
		} else if(res == 27 || res == KEY_F(F_BACK) ||
		          res == KEY_F(F_EXIT)) {
			res = KEY_EXIT;
			break;
		}
	}

	unpost_menu(menu);
	free_menu(menu);

	for(i = 0; i < btn_num; i++)
		free_item(btns[i]);

	delwin(win);
	return res;
}

int dialog_inputbox(WINDOW* main_window,
                    const char* title, const char* prompt,
                    const char* init, char** resultp, int* result_len)
{
	int prompt_lines = 0;
	int prompt_width = 0;
	WINDOW* win;
	WINDOW* prompt_win;
	WINDOW* form_win;
	PANEL* panel;
	int i, x, y, lines, columns, win_lines, win_cols;
	int res = -1;
	int cursor_position = strlen(init);
	int cursor_form_win;
	char* result = *resultp;

	getmaxyx(stdscr, lines, columns);

	if(strlen(init) + 1 > *result_len) {
		*result_len = strlen(init) + 1;
		*resultp = result = realloc(result, *result_len);
	}

	/* find the widest line of msg: */
	prompt_lines = get_line_no(prompt);

	for(i = 0; i < prompt_lines; i++) {
		const char* line = get_line(prompt, i);
		int len = get_line_length(line);
		prompt_width = max(prompt_width, len);
	}

	if(title)
		prompt_width = max(prompt_width, strlen(title));

	win_lines = min(prompt_lines + 6, lines - 2);
	win_cols = min(prompt_width + 7, columns - 2);
	prompt_lines = max(win_lines - 6, 0);
	prompt_width = max(win_cols - 7, 0);

	/* place dialog in middle of screen */
	y = (lines - win_lines) / 2;
	x = (columns - win_cols) / 2;

	strncpy(result, init, *result_len);

	/* create the windows */
	win = newwin(win_lines, win_cols, y, x);
	prompt_win = derwin(win, prompt_lines + 1, prompt_width, 2, 2);
	form_win = derwin(win, 1, prompt_width, prompt_lines + 3, 2);
	keypad(form_win, TRUE);

	(void) wattrset(form_win, attributes[INPUT_FIELD]);

	(void) wattrset(win, attributes[INPUT_BOX]);
	box(win, 0, 0);
	(void) wattrset(win, attributes[INPUT_HEADING]);

	if(title)
		mvwprintw(win, 0, 3, "%s", title);

	/* print message */
	(void) wattrset(prompt_win, attributes[INPUT_TEXT]);
	fill_window(prompt_win, prompt);

	mvwprintw(form_win, 0, 0, "%*s", prompt_width, " ");
	cursor_form_win = min(cursor_position, prompt_width - 1);
	mvwprintw(form_win, 0, 0, "%s",
	          result + cursor_position - cursor_form_win);

	/* create panels */
	panel = new_panel(win);

	/* show the cursor */
	curs_set(1);

	touchwin(win);
	refresh_all_windows(main_window);

	while((res = wgetch(form_win))) {
		int len = strlen(result);

		switch(res) {
			case 10: /* ENTER */
			case 27: /* ESCAPE */
			case KEY_F(F_HELP):
			case KEY_F(F_EXIT):
			case KEY_F(F_BACK):
				break;

			case 127:
			case KEY_BACKSPACE:
				if(cursor_position > 0) {
					memmove(&result[cursor_position - 1],
					        &result[cursor_position],
					        len - cursor_position + 1);
					cursor_position--;
					cursor_form_win--;
					len--;
				}

				break;

			case KEY_DC:
				if(cursor_position >= 0 && cursor_position < len) {
					memmove(&result[cursor_position],
					        &result[cursor_position + 1],
					        len - cursor_position + 1);
					len--;
				}

				break;

			case KEY_UP:
			case KEY_RIGHT:
				if(cursor_position < len) {
					cursor_position++;
					cursor_form_win++;
				}

				break;

			case KEY_DOWN:
			case KEY_LEFT:
				if(cursor_position > 0) {
					cursor_position--;
					cursor_form_win--;
				}

				break;

			case KEY_HOME:
				cursor_position = 0;
				cursor_form_win = 0;
				break;

			case KEY_END:
				cursor_position = len;
				cursor_form_win = min(cursor_position, prompt_width - 1);
				break;

			default:
				if((isgraph(res) || isspace(res))) {
					/* one for new char, one for '\0' */
					if(len + 2 > *result_len) {
						*result_len = len + 2;
						*resultp = result = realloc(result,
						                            *result_len);
					}

					/* insert the char at the proper position */
					memmove(&result[cursor_position + 1],
					        &result[cursor_position],
					        len - cursor_position + 1);
					result[cursor_position] = res;
					cursor_position++;
					cursor_form_win++;
					len++;
				} else {
					mvprintw(0, 0, "unknown key: %d\n", res);
				}

				break;
		}

		if(cursor_form_win < 0)
			cursor_form_win = 0;
		else if(cursor_form_win > prompt_width - 1)
			cursor_form_win = prompt_width - 1;

		wmove(form_win, 0, 0);
		wclrtoeol(form_win);
		mvwprintw(form_win, 0, 0, "%*s", prompt_width, " ");
		mvwprintw(form_win, 0, 0, "%s",
		          result + cursor_position - cursor_form_win);
		wmove(form_win, 0, cursor_form_win);
		touchwin(win);
		refresh_all_windows(main_window);

		if(res == 10) {
			res = 0;
			break;
		} else if(res == 27 || res == KEY_F(F_BACK) ||
		          res == KEY_F(F_EXIT)) {
			res = KEY_EXIT;
			break;
		} else if(res == KEY_F(F_HELP)) {
			res = 1;
			break;
		}
	}

	/* hide the cursor */
	curs_set(0);
	del_panel(panel);
	delwin(prompt_win);
	delwin(form_win);
	delwin(win);
	return res;
}

/* refresh all windows in the correct order */
void refresh_all_windows(WINDOW* main_window)
{
	update_panels();
	touchwin(main_window);
	refresh();
}

/* layman's scrollable window... */
void show_scroll_win(WINDOW* main_window,
                     const char* title,
                     const char* text)
{
	int res;
	int total_lines = get_line_no(text);
	int x, y, lines, columns;
	int start_x = 0, start_y = 0;
	int text_lines = 0, text_cols = 0;
	int total_cols = 0;
	int win_cols = 0;
	int win_lines = 0;
	int i = 0;
	WINDOW* win;
	WINDOW* pad;
	PANEL* panel;

	getmaxyx(stdscr, lines, columns);

	/* find the widest line of msg: */
	total_lines = get_line_no(text);

	for(i = 0; i < total_lines; i++) {
		const char* line = get_line(text, i);
		int len = get_line_length(line);
		total_cols = max(total_cols, len + 2);
	}

	/* create the pad */
	pad = newpad(total_lines + 10, total_cols + 10);
	(void) wattrset(pad, attributes[SCROLLWIN_TEXT]);
	fill_window(pad, text);

	win_lines = min(total_lines + 4, lines - 2);
	win_cols = min(total_cols + 2, columns - 2);
	text_lines = max(win_lines - 4, 0);
	text_cols = max(win_cols - 2, 0);

	/* place window in middle of screen */
	y = (lines - win_lines) / 2;
	x = (columns - win_cols) / 2;

	win = newwin(win_lines, win_cols, y, x);
	keypad(win, TRUE);
	/* show the help in the help window, and show the help panel */
	(void) wattrset(win, attributes[SCROLLWIN_BOX]);
	box(win, 0, 0);
	(void) wattrset(win, attributes[SCROLLWIN_HEADING]);
	mvwprintw(win, 0, 3, " %s ", title);
	panel = new_panel(win);

	/* handle scrolling */
	do {

		copywin(pad, win, start_y, start_x, 2, 2, text_lines,
		        text_cols, 0);
		print_in_middle(win,
		                text_lines + 2,
		                0,
		                text_cols,
		                "<OK>",
		                attributes[DIALOG_MENU_FORE]);
		wrefresh(win);

		res = wgetch(win);

		switch(res) {
			case KEY_NPAGE:
			case ' ':
			case 'd':
				start_y += text_lines - 2;
				break;

			case KEY_PPAGE:
			case 'u':
				start_y -= text_lines + 2;
				break;

			case KEY_HOME:
				start_y = 0;
				break;

			case KEY_END:
				start_y = total_lines - text_lines;
				break;

			case KEY_DOWN:
			case 'j':
				start_y++;
				break;

			case KEY_UP:
			case 'k':
				start_y--;
				break;

			case KEY_LEFT:
			case 'h':
				start_x--;
				break;

			case KEY_RIGHT:
			case 'l':
				start_x++;
				break;
		}

		if(res == 10 || res == 27 || res == 'q' ||
		        res == KEY_F(F_HELP) || res == KEY_F(F_BACK) ||
		        res == KEY_F(F_EXIT))
			break;

		if(start_y < 0)
			start_y = 0;

		if(start_y >= total_lines - text_lines)
			start_y = total_lines - text_lines;

		if(start_x < 0)
			start_x = 0;

		if(start_x >= total_cols - text_cols)
			start_x = total_cols - text_cols;
	} while(res);

	del_panel(panel);
	delwin(win);
	refresh_all_windows(main_window);
}
